<template>
	<view class="content">
		<view class="btn-box">
			<view v-if="connected" style="font-size: 24rpx;color: green;">已链接</view>
			<view v-else style="font-size: 24rpx;color: red;">链接断开</view>
			<button type="primary" size="mini" style="margin: 0;" :disabled="!connected" @click="stopConnection">断开链接</button>
		</view>
		<view v-if="errMsg" class="err-info">{{errMsg}}</view>
		<view class="service-content">
			<view v-for="item in deviceServices" :key="item.uuid" class="service-item">
				<view style="font-size: 24rpx; color: #333">UUID:{{item.uuid}}</view>
				<view style="font-size: 20rpx">isPrimary:{{item.isPrimary}}</view>
				<view class="characteristic-box">
					<view v-for="cItem in item.characteristics" :key="cItem.uuid" class="characteristic-item">
						<view style="font-size: 24rpx; color: #333">UUID:{{cItem.uuid}}</view>
						<view style="font-size: 20rpx">properties:</view>
						<view v-for="(pItem,key) in cItem.properties" :key="key" style="font-size: 20rpx;margin-left: 30rpx;">{{key}}:{{pItem}}</view>
						<view class="btn-box">
							<button type="primary" :disabled="!cItem.properties.write" size="mini" @click="sendData(item,cItem)">发送</button>
							<button type="primary" :disabled="!cItem.properties.read" size="mini" @click="readData(item,cItem)">读取</button>
						</view>
					</view>
				</view>
			</view>
		</view>
		<!-- 底部填充-底部安全区域高度 -->
		<view :style="{height:safeAreaInsets.bottom+'px'}" />
	</view>
</template>

<script lang="ts" setup>
	import {ref} from 'vue';
	import {onLoad,onUnload} from '@dcloudio/uni-app';
	import {windowInfoStore} from '@/stores/windowInfo';
	
	interface IService {
		uuid:string;
		isPrimary:boolean;
		characteristics:{
			uuid:string;
			properties:{
				read:boolean;
				write:boolean;
				notify:boolean;
				indicate:boolean;
				writeNoResponse:boolean;
				writeDefault:boolean;
			}
		}[]
	}
	
	const windowStore = windowInfoStore();
	const safeAreaInsets = windowStore.safeAreaInsets;
		
	let deviceId:string|null = null;
	const errMsg = ref('');
	
	const connected = ref(false);
	const deviceServices = ref<IService[]>([]);
	
	onLoad((option)=>{
		deviceId = option.deviceId as string;
		deviceConnection(deviceId);
		//监听蓝牙低功耗设备的特征值变化事件
		uni.onBLECharacteristicValueChange((res)=>{
			console.log(res.value);
			uni.showModal({
				title: '读取',
				content: ab2hex(res.value),
				showCancel:false,
			});
		});
		//监听蓝牙连接状态变化
		uni.onBLEConnectionStateChange(listenerConnectionStateChange);
	});
	
	onUnload(()=>{
		//移除蓝牙连接状态变化监听
		uni.offBLEConnectionStateChange(listenerConnectionStateChange);
		//移除监听蓝牙低功耗设备的特征值变化事件
		uni.offBLECharacteristicValueChange();
	});
	
	//蓝牙连接状态变化处理函数
	function listenerConnectionStateChange(res:UniApp.OnBLEConnectionStateChangeSuccess){
		if(res.deviceId===deviceId){
			connected.value = res.connected;
		}
	}
	
	//蓝牙设备连接
	function deviceConnection(deviceId:string){
		uni.showLoading({title: '正在加载',mask:true});
		uni.createBLEConnection({deviceId}).then(res=>{
			uni.hideLoading();
			console.log(res);
			connected.value = true;
			getDeviceServices(deviceId)
		}).catch((err)=>{
			uni.hideLoading();
			console.log(err);
			uni.showToast({
				icon:'none',
				title:err.errMsg
			})
			errMsg.value = err.errMsg;
		});
	} 
	
	//获取蓝牙设备服务
	async function getDeviceServices(deviceId:string){
		const res = await uni.getBLEDeviceServices({ deviceId });
		return await getDeviceCharacteristics(deviceId, res.services);
	}
	
	//获取蓝牙设备服务特征
	async function getDeviceCharacteristics(deviceId:string,services:UniApp.GetBLEDeviceServicesSuccessData[]){
		for(const item of services){
			const characteristics = await uni.getBLEDeviceCharacteristics({deviceId,serviceId:item.uuid}).then(res=>{
				return res.characteristics
			});
			const service = {...item,characteristics}
			deviceServices.value.push(service);
		}
		console.log(deviceServices.value)
	}
	
	//读取数据
	function readData(service:IService,characteristic:IService['characteristics'][number]){
		uni.showLoading({title: '正在加载',mask:true});
		uni.readBLECharacteristicValue({deviceId,serviceId:service.uuid,characteristicId:characteristic.uuid}).then(()=>{
			uni.hideLoading();
		}).catch((err)=>{
			uni.hideLoading();
			console.log(err);
			uni.showToast({
				icon:'none',
				title:err.errMsg
			});
		});
	}
	
	//发送数据
	function sendData(service:IService,characteristic:IService['characteristics'][number]){
		uni.showModal({
			title: '发送',
			content: '',
			editable:true,
			success: (res)=> {
				if (res.confirm) {
					writeCharacteristicValue(service.uuid,characteristic.uuid,stringToArrayBuffer(res.content!))
				} 
			}
		});
	}
	
	//向蓝牙低功耗设备特征值中写入二进制数据
	function writeCharacteristicValue(serviceId:string,characteristicId:string,value:any){
		uni.showLoading({title: '正在加载',mask:true});
		uni.writeBLECharacteristicValue({deviceId,serviceId,characteristicId,value}).then(res=>{
			uni.hideLoading();
			console.log(res);
			uni.showToast({
				icon:'none',
				title:'发送成功'
			});
		}).catch((err)=>{
			uni.hideLoading();
			console.log(err);
			uni.showToast({
				icon:'none',
				title:err.errMsg
			});
			errMsg.value = err.errMsg;
		});
	}
	
	//字符串装二进制数据
	function stringToArrayBuffer(str:string) {
		const buffer = new ArrayBuffer(str.length);
		const dataView = new Uint8Array(buffer);
		for (let i = 0; i < str.length; i++) {
			dataView[i] = str.charCodeAt(i);
		}
		return buffer;
	}
	
	//二进制转十六进制字符串
	function ab2hex(buffer:any[]) {
		const hexArr = Array.prototype.map.call(new Uint8Array(buffer),(bit:number)=> ('00' + bit.toString(16)).slice(-2));
		return hexArr.join(',');
	}
	
	//断开连接
	function stopConnection(){
		uni.showLoading({title: '正在加载',mask:true});
		uni.closeBLEConnection({deviceId}).then(()=>{
			uni.hideLoading();
			uni.showToast({
				icon:'none',
				title:'已断开',
				complete: () => {
					setTimeout(()=>{
						uni.navigateBack();
					},1500);
				}
			})
		}).catch((err)=>{
			uni.hideLoading();
			uni.showToast({
				icon:'none',
				title:err.errMsg
			});
			errMsg.value = err.errMsg;
		});
	}
</script>

<style lang="scss" scoped>
	.content {
		margin: 0 30rpx;
		height: 100vh;
		display: flex;
		flex-direction: column;
		gap: 20rpx;
		.btn-box {
			display: flex;
			flex-direction: row;
			justify-content: space-between;
			align-items: center;
		}
		.err-info {
			font-size: 24rpx;
			color: red;
		}
		.service-content {
			flex: 1;
			min-height: 0;
			box-sizing: border-box;
			display: flex;
			flex-direction: column;
			gap: 20rpx;
			.service-item {
				border: 1px solid #f1f1f1;
				border-radius: 10rpx;
				padding: 20rpx;
				.characteristic-box {
					background-color: #f1f1f1;
					border-radius: 10rpx;
					padding: 15rpx;
					display: flex;
					flex-direction: column;
					gap: 20rpx;
					.characteristic-item {
						border-bottom: 1rpx solid #cdcdcd;
						.btn-box {
							display: flex;
							flex-direction: row;
							justify-content: space-between;
							margin: 10rpx 0;
						}
					}
				}
			}
		}
	}
</style>